package com.lazyboyl.big.data.read.util;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 类描述：redis操作类
 *
 * @author linzf
 */
public class RedisCache {

    @Autowired
    private RedisTemplate<String, ?> redisTemplate;


    /**
     * 获取唯一时间戳的lua脚本，精确到毫秒
     * redis.replicate_commands()必须要加，然后这会报错
     * 参考文献：https://blog.csdn.net/qq_39954022/article/details/77866360
     */
    public static final String getUniqueTimeStamp = "redis.replicate_commands()\n"
            + "local a=redis.call('TIME')\n "
            + "local t = (a[1]*1000000+a[2])/1000 \n"
            + "while (tonumber(redis.call(\"get\",KEYS[1])) == t)\n"
            + "do\n"
            + "     a=redis.call('TIME')\n"
            + "     t= (a[1]*1000000+a[2])/1000 \n"
            + "end\n"
            + "redis.call(\"set\",KEYS[1],t)\n"
            + "return t\n";


    /**
     * 解锁脚本，原子操作
     */
    private static final String unlockScript =
            "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
                    + "then\n"
                    + "    return redis.call(\"del\",KEYS[1])\n"
                    + "else\n"
                    + "    return 0\n"
                    + "end";

    /**
     * 功能描述： 获取唯一时间戳
     *
     * @param key 通过key来获取这个key的唯一时间戳
     * @return 返回唯一时间戳
     */
    public Long getUniqueTimeStamp(String key) {
        Long result = redisTemplate.execute((RedisCallback<Long>) redisConnection -> {
            byte[][] keysAndArgs = new byte[1][];
            keysAndArgs[0] = key.getBytes(Charset.forName("UTF-8"));
            Long result1 = redisConnection.scriptingCommands().eval(getUniqueTimeStamp.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keysAndArgs);
            return result1;
        });
        return result;
    }

    /**
     * 功能描述： 若有值则返回
     *
     * @param key    当前需要设置的key
     * @param value  key的value的值
     * @param expire 过期时间
     * @return true: 表示已经不存在这个值；false：表示存在这个值
     */
    public Boolean setNx(String key, String value, Long expire) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                Object obj = connection.execute("set", keySerializer.serialize(key),
                        valueSerializer.serialize(value),
                        "NX".getBytes(StandardCharsets.UTF_8),
                        "EX".getBytes(StandardCharsets.UTF_8),
                        String.valueOf(expire).getBytes(StandardCharsets.UTF_8));
                return obj != null;
            }
        });
        return result;
    }

    /**
     * 功能描述：使用lua脚本的形式来实现解锁
     *
     * @param key       需要解锁的key
     * @param requestId 枷锁的唯一标识
     * @return 返回枷锁结果
     */
    public boolean unlock(String key, String requestId) {
        String result = redisTemplate.execute((RedisCallback<String>) redisConnection -> {
            byte[][] keysAndArgs = new byte[2][];
            keysAndArgs[0] = key.getBytes(Charset.forName("UTF-8"));
            keysAndArgs[1] = requestId.getBytes(Charset.forName("UTF-8"));
            Long result1 = redisConnection.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keysAndArgs);
            if (result1 != null && result1 > 0) {
                return "SUCESS";
            } else {
                return "FAIL";
            }
        });
        if (result.equals("SUCESS")) {
            return true;
        }
        return false;
    }


    /**
     * 加锁，有阻塞加锁，若被锁住则会不断的尝试直到等待时间全部耗尽或者获取锁为止
     *
     * @param key       枷锁的key
     * @param requestId 枷锁的唯一标识
     * @param expire    过期时间（豪秒）
     * @param timeout   等待时间（毫秒），超过多少毫秒就直接返回获取锁失败
     * @return
     */
    public String lock(String key, String requestId, long expire, long timeout) {
        long startTime = System.currentTimeMillis();
        String token;
        do {
            token = tryLock(key, requestId, expire);
            System.out.println("我在尝试获取锁：" + token);
            if (token == null) {
                if ((System.currentTimeMillis() - startTime) > (timeout - 50)) {
                    break;
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        } while (token == null);

        return token;
    }


    /**
     * 功能描述： 实现无阻塞枷锁
     *
     * @param key       枷锁的key
     * @param requestId 枷锁的唯一标识
     * @param expire    过期时间（毫秒）
     * @return 返回加锁结果
     */
    public String tryLock(String key, String requestId, long expire) {
        return redisTemplate.execute((RedisCallback<String>) redisConnection -> {
            Boolean result = redisConnection.set(key.getBytes(Charset.forName("UTF-8")),
                    requestId.getBytes(Charset.forName("UTF-8")),
                    Expiration.from(expire, TimeUnit.MILLISECONDS),
                    RedisStringCommands.SetOption.SET_IF_ABSENT);
            if (result != null && result) {
                return requestId;
            }
            return null;
        });
    }

    /**
     * 功能描述：判断key是否存在
     *
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public boolean setString(String key, String value) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                return true;
            }
        });
        return result;
    }

    public boolean setString(String key, String value, int time) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                if (time > 0) {
                    connection.expire(serializer.serialize(key), time);
                }
                return true;
            }
        });
        return result;
    }

    public String getString(String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] value = connection.get(serializer.serialize(key));
                return serializer.deserialize(value);
            }
        });
        return result;
    }


    public boolean expire(String key, long expire) {
        return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }


    public <T> boolean setList(String key, List<T> list) {
        String value = JsonUtils.objToJson(list);
        return setString(key, value);
    }


    public <T> List<T> getList(final String key, Class<T> clz) {
        String json = getString(key);
        if (json != null) {
            List<T> list = JsonUtils.jsonToList(json, clz);
            return list;
        }
        return null;
    }

    /**
     * 功能描述：从redis中获取数据
     *
     * @param key
     * @param elementType
     * @param <T>
     * @return
     */
    public <T> T getObject(final String key, Class<T> elementType) {
        return redisTemplate.execute(new RedisCallback<T>() {
            @Override
            public T doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(keybytes)) {
                    byte[] valuebytes = connection.get(keybytes);
                    @SuppressWarnings("unchecked")
                    T value = (T) SerializeUtil.unserialize(valuebytes);
                    return value;
                }
                return null;
            }
        });
    }

    /**
     * 功能描述：设值到redis中
     *
     * @param key
     * @param obj
     */
    public void setObject(String key, Object obj) {
        final byte[] bytes = SerializeUtil.serialize(obj);
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection)
                    throws DataAccessException {
                connection.set(redisTemplate.getStringSerializer().serialize(key), bytes);
                return null;
            }
        });
    }

    /**
     * 功能描述：设值到redis中
     *
     * @param key
     * @param obj
     * @param time
     */
    public void setObject(String key, Object obj, int time) {
        final byte[] bytes = SerializeUtil.serialize(obj);
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection)
                    throws DataAccessException {
                connection.set(redisTemplate.getStringSerializer().serialize(key), bytes);
                return null;
            }
        });
        this.expire(key, time);
    }


    public long rPush(String key, Object obj) {
        String value = JsonUtils.objToJson(obj);
        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.rPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
        });
        return result;
    }

    /**
     * 功能描述： 将相应的值放入到队列中
     *
     * @param key 队列的key值
     * @param obj 需要插入到队列中的值
     * @return 返回队列位置
     */
    public Long lPush(String key, Object obj) {
        String value = JsonUtils.objToJson(obj);
        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.lPush(serializer.serialize(key), serializer.serialize(value));
                return count;
            }
        });
        return result;
    }

    /**
     * 功能描述： 将字符串转换为相应的对象
     *
     * @param key   list的key的值
     * @param clazz 需要转换的对象
     * @param <T>   泛型
     * @return 返回相应的对象
     */
    public <T> T rPop(String key, Class<T> clazz) {
        String val = rPop(key);
        if (val == null || val.equals("")) {
            return null;
        }
        return JsonUtils.jsonToPojo(val, clazz);
    }

    /**
     * 功能描述： 对队列的末尾取出相应的值
     *
     * @param key 队列的key的名称
     * @return 返回取出的结果
     */
    public String rPop(String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] res = connection.rPop(serializer.serialize(key));
                return serializer.deserialize(res);
            }
        });
        return result;
    }


    public String lPop(String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] res = connection.lPop(serializer.serialize(key));
                return serializer.deserialize(res);
            }
        });
        return result;
    }


    public void del(String key) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection)
                    throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.del(serializer.serialize(key));
                return null;
            }
        });
    }

    public Long getTime(String key) {
        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                long count = connection.ttl(serializer.serialize(key));
                return count;
            }
        });
        return result;
    }

}
